summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server')
-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
128 files changed, 148569 insertions, 0 deletions
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)