From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../src-server/darwin/HostDnsServiceDarwin.cpp | 281 +++ .../Main/src-server/darwin/HostPowerDarwin.cpp | 252 +++ src/VBox/Main/src-server/darwin/Makefile.kup | 0 src/VBox/Main/src-server/darwin/NetIf-darwin.cpp | 558 ++++++ .../Main/src-server/darwin/PerformanceDarwin.cpp | 191 ++ .../src-server/darwin/USBProxyBackendDarwin.cpp | 195 ++ src/VBox/Main/src-server/darwin/iokit.cpp | 1993 ++++++++++++++++++++ src/VBox/Main/src-server/darwin/iokit.h | 117 ++ 8 files changed, 3587 insertions(+) create mode 100644 src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp create mode 100644 src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp create mode 100644 src/VBox/Main/src-server/darwin/Makefile.kup create mode 100644 src/VBox/Main/src-server/darwin/NetIf-darwin.cpp create mode 100644 src/VBox/Main/src-server/darwin/PerformanceDarwin.cpp create mode 100644 src/VBox/Main/src-server/darwin/USBProxyBackendDarwin.cpp create mode 100644 src/VBox/Main/src-server/darwin/iokit.cpp create mode 100644 src/VBox/Main/src-server/darwin/iokit.h (limited to 'src/VBox/Main/src-server/darwin') 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..0d0b0d5c --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include + + +#include +#include +#include +#include + +#include +#include + +#include +#include +#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 vrc = RTSemEventCreate(&m->m_evtStop); + AssertRCReturn(vrc, 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 vrc = updateInfo(); + AssertRC(vrc); /* 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 + * \ { + * DomainName : vvl-domain + * SearchDomains : \ { + * 0 : vvl-domain + * 1 : de.vvl-domain.com + * } + * ServerAddresses : \ { + * 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(propertyRef), CFSTR("DomainName")); + if (domainNameRef) + { + const char *pszDomainName = CFStringGetCStringPtr(domainNameRef, CFStringGetSystemEncoding()); + if (pszDomainName) + info.domain = pszDomainName; + } + + CFArrayRef serverArrayRef = (CFArrayRef)CFDictionaryGetValue(static_cast(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(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..c86924a8 --- /dev/null +++ b/src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp @@ -0,0 +1,252 @@ +/* $Id: HostPowerDarwin.cpp $ */ +/** @file + * VirtualBox interface to host's power notification service, darwin specifics. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN_HOST +#include "HostPower.h" +#include "LoggingNew.h" +#include + +#include +#include +#include + +#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 vrc = RTThreadCreate(&mThread, HostPowerServiceDarwin::powerChangeNotificationThread, this, 65536, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "MainPower"); + AssertLogRelRC(vrc); +} + +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(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(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(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(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(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 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..074c297c --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#include +#undef PVM + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HostNetworkInterfaceImpl.h" +#include "netif.h" +#include "iokit.h" +#include "LoggingNew.h" + +#if 0 +int NetIfList(std::list > &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 iRc = getifaddrs(&IfAddrs); + if (iRc) + { + close(sock); + Log(("NetIfList: getifaddrs() -> %d\n", iRc)); + 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 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)); + //vrc = 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 > &list) +{ + int vrc = 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. */ + vrc = getDefaultIfaceIndex(&u16DefaultIface); + if (RT_FAILURE(vrc)) + return vrc; + + 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)); + vrc = 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) + { + vrc = 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 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 vrc; +} + +int NetIfGetConfigByName(PNETIFINFO pInfo) +{ + int vrc = 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)); + vrc = 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 vrc; +} + +/** + * 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..9448093f --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 vrc = RTSystemQueryTotalRam(&cb); + if (RT_FAILURE(vrc)) + 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 vrc = RTSystemQueryAvailableRam(&cb); + if (RT_SUCCESS(vrc)) + { + *total = totalRAM; + cb /= 1024; + *available = cb < ~(ULONG)0 ? (ULONG)cb : ~(ULONG)0; + *used = *total - *available; + } + return vrc; +} + +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 vrc = getProcessInfo(process, &tinfo); + if (RT_SUCCESS(vrc)) + { + /* + * 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 vrc; +} + +int CollectorDarwin::getProcessMemoryUsage(RTPROCESS process, ULONG *used) +{ + struct proc_taskinfo tinfo; + + int vrc = getProcessInfo(process, &tinfo); + if (RT_SUCCESS(vrc)) + { + uint64_t cKbResident = tinfo.pti_resident_size / 1024; + *used = cKbResident < ~(ULONG)0 ? (ULONG)cKbResident : ~(ULONG)0; + } + return vrc; +} + +} + 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..81300a0f --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include + + +/** + * 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..5fd1aba1 --- /dev/null +++ b/src/VBox/Main/src-server/darwin/iokit.cpp @@ -0,0 +1,1993 @@ +/* $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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN +#ifdef STANDALONE_TESTCASE +# define VBOX_WITH_USB +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef VBOX_WITH_USB +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STANDALONE_TESTCASE +# include +# include +#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 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, &szVersion[0], sizeof(szVersion)); + if (RT_SUCCESS(vrc)) + { + vrc = RTStrToUInt32Ex(&szVersion[0], NULL, 10, &g_uMajorDarwin); + AssertLogRelMsg(vrc == VINF_SUCCESS || vrc == VWRN_TRAILING_CHARS, + ("Failed to convert the major part of the version string '%s' into an integer: %Rrc\n", + szVersion, vrc)); + } + else + AssertLogRelMsgFailed(("Failed to query the OS release version with %Rrc\n", vrc)); + } + 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 +# 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 krc = IOServiceAddMatchingNotification(pNotify->NotifyPort, + kIOPublishNotification, + IOServiceMatching(kIOUSBDeviceClassName), + darwinUSBAttachNotification1, + pNotify, + &pNotify->AttachIterator); + if (krc == KERN_SUCCESS) + { + darwinDrainIterator(pNotify->AttachIterator); + krc = IOServiceAddMatchingNotification(pNotify->NotifyPort, + kIOMatchedNotification, + IOServiceMatching(kIOUSBDeviceClassName), + darwinUSBAttachNotification2, + pNotify, + &pNotify->AttachIterator2); + if (krc == KERN_SUCCESS) + { + darwinDrainIterator(pNotify->AttachIterator2); + krc = IOServiceAddMatchingNotification(pNotify->NotifyPort, + kIOTerminatedNotification, + IOServiceMatching(kIOUSBDeviceClassName), + darwinUSBDetachNotification, + pNotify, + &pNotify->DetachIterator); + if (krc == KERN_SUCCESS) + { + 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 irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 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). + * + * @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 irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 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 <= 4); + pCur->enmSpeed = bSpeed == 4 ? USBDEVICESPEED_SUPER + : 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; + irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppUSBDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppUSBDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == 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 irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &DVDServices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 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 irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &MediaServices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 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 " 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 irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &EtherIfServices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), 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..67c8d3c6 --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * 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 +#include +#include +#ifdef VBOX_WITH_USB +# include +#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 */ -- cgit v1.2.3