diff options
Diffstat (limited to 'src/VBox/Main/src-server')
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, + ¤t->debugging, + ¤t->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(¤t->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(¤t->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) |