File indexing completed on 2024-05-26 04:05:33

0001 /*
0002     SPDX-FileCopyrightText: 2009 Harald Fernengel <harry@kdevelop.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "iokitmanager.h"
0008 #include "iokitdevice.h"
0009 
0010 #include <qdebug.h>
0011 
0012 #include <IOKit/IOKitLib.h>
0013 #include <IOKit/network/IOEthernetInterface.h>
0014 #include <IOKit/usb/IOUSBLib.h>
0015 
0016 #include <CoreFoundation/CoreFoundation.h>
0017 
0018 namespace Solid
0019 {
0020 namespace Backends
0021 {
0022 namespace IOKit
0023 {
0024 class IOKitManagerPrivate
0025 {
0026 public:
0027     inline IOKitManagerPrivate()
0028         : port(nullptr)
0029         , source(nullptr)
0030     {
0031     }
0032 
0033     IONotificationPortRef port;
0034     CFRunLoopSourceRef source;
0035 
0036     static const char *typeToName(Solid::DeviceInterface::Type type);
0037     static QStringList devicesFromRegistry(io_iterator_t it);
0038 
0039     QSet<Solid::DeviceInterface::Type> supportedInterfaces;
0040 };
0041 
0042 // gets all registry paths from an iterator
0043 QStringList IOKitManagerPrivate::devicesFromRegistry(io_iterator_t it)
0044 {
0045     QStringList result;
0046     io_object_t obj;
0047     while ((obj = IOIteratorNext(it))) {
0048         CFStringRef pathRef = IORegistryEntryCopyPath(obj, kIOServicePlane);
0049         const QString path = QString::fromCFString(pathRef);
0050         CFRelease(pathRef);
0051 
0052         if (path.isEmpty()) {
0053             qWarning() << Q_FUNC_INFO << "IORegistryEntryCopyPath failed";
0054             continue;
0055         }
0056         result += path;
0057         const kern_return_t ret = IOObjectRelease(obj);
0058         if (ret != KERN_SUCCESS) {
0059             // very unlikely to happen - keep it a qDebug just in case.
0060             // compiler will nuke this code in release builds.
0061             qDebug() << Q_FUNC_INFO << "Unable to release object reference";
0062         }
0063     }
0064     IOObjectRelease(it);
0065 
0066     return result;
0067 }
0068 
0069 const char *IOKitManagerPrivate::typeToName(Solid::DeviceInterface::Type type)
0070 {
0071     switch (type) {
0072     case Solid::DeviceInterface::Unknown:
0073         return 0;
0074     case Solid::DeviceInterface::Processor:
0075         return "AppleACPICPU";
0076     case Solid::DeviceInterface::Battery:
0077         return "AppleSmartBattery";
0078 
0079         // Solid::DeviceInterface::GenericInterface:
0080         // Solid::DeviceInterface::Block:
0081     case Solid::DeviceInterface::StorageAccess:
0082     case Solid::DeviceInterface::StorageDrive:
0083     case Solid::DeviceInterface::StorageVolume:
0084         return "IOMedia";
0085     case Solid::DeviceInterface::OpticalDrive:
0086     case Solid::DeviceInterface::OpticalDisc:
0087         return "IOCDMedia";
0088         // Solid::DeviceInterface::Camera:
0089         // Solid::DeviceInterface::PortableMediaPlayer:
0090     }
0091 
0092     return 0;
0093 }
0094 
0095 IOKitManager::IOKitManager(QObject *parent)
0096     : Solid::Ifaces::DeviceManager(parent)
0097     , d(new IOKitManagerPrivate)
0098 {
0099     d->port = IONotificationPortCreate(kIOMasterPortDefault);
0100     if (!d->port) {
0101         qWarning() << Q_FUNC_INFO << "Unable to create notification port";
0102         return;
0103     }
0104 
0105     d->source = IONotificationPortGetRunLoopSource(d->port);
0106     if (!d->source) {
0107         qWarning() << Q_FUNC_INFO << "Unable to create notification source";
0108         return;
0109     }
0110 
0111     CFRunLoopAddSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode);
0112     // clang-format off
0113     d->supportedInterfaces << Solid::DeviceInterface::GenericInterface
0114                            << Solid::DeviceInterface::Processor
0115                            << Solid::DeviceInterface::Block
0116                            << Solid::DeviceInterface::StorageAccess
0117                            << Solid::DeviceInterface::StorageDrive
0118                            << Solid::DeviceInterface::OpticalDrive
0119                            << Solid::DeviceInterface::StorageVolume
0120                            << Solid::DeviceInterface::OpticalDisc
0121                            << Solid::DeviceInterface::Camera
0122                            << Solid::DeviceInterface::PortableMediaPlayer
0123                            << Solid::DeviceInterface::Battery;
0124     // clang-format on
0125 }
0126 
0127 IOKitManager::~IOKitManager()
0128 {
0129     if (d->source) {
0130         CFRunLoopRemoveSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode);
0131     }
0132     if (d->port) {
0133         IONotificationPortDestroy(d->port);
0134     }
0135 
0136     delete d;
0137 }
0138 
0139 QString IOKitManager::udiPrefix() const
0140 {
0141     return QString(); // FIXME: We should probably use a prefix there... has to be tested on Mac
0142 }
0143 
0144 QSet<Solid::DeviceInterface::Type> IOKitManager::supportedInterfaces() const
0145 {
0146     return d->supportedInterfaces;
0147 }
0148 
0149 QStringList IOKitManager::allDevices()
0150 {
0151     // use an IORegistry Iterator to iterate over all devices in the service plane
0152 
0153     io_iterator_t it;
0154     kern_return_t ret = IORegistryCreateIterator(kIOMasterPortDefault, kIOServicePlane, kIORegistryIterateRecursively, &it);
0155     if (ret != KERN_SUCCESS) {
0156         qWarning() << Q_FUNC_INFO << "unable to create iterator";
0157         return QStringList();
0158     }
0159 
0160     return IOKitManagerPrivate::devicesFromRegistry(it);
0161 }
0162 
0163 QStringList IOKitManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type)
0164 {
0165     QStringList result;
0166 
0167     if (type == Solid::DeviceInterface::Unknown) {
0168         // match all device interfaces
0169         result = allDevices();
0170     } else {
0171         const char *deviceClassName = IOKitManagerPrivate::typeToName(type);
0172         if (!deviceClassName) {
0173             return QStringList();
0174         }
0175 
0176         CFMutableDictionaryRef matchingDict = IOServiceMatching(deviceClassName);
0177 
0178         if (!matchingDict) {
0179             return QStringList();
0180         }
0181 
0182         io_iterator_t it = 0;
0183 
0184         // note - IOServiceGetMatchingServices dereferences the dict
0185         kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &it);
0186 
0187         result = IOKitManagerPrivate::devicesFromRegistry(it);
0188     }
0189 
0190     // if the parentUdi is an empty string, return all matches
0191     if (parentUdi.isEmpty()) {
0192         return result;
0193     }
0194 
0195     // return only matches that start with the parent's UDI
0196     QStringList filtered;
0197     for (const QString &udi : std::as_const(result)) {
0198         if (udi.startsWith(parentUdi)) {
0199             filtered += udi;
0200         }
0201     }
0202 
0203     return filtered;
0204 }
0205 
0206 QObject *IOKitManager::createDevice(const QString &udi)
0207 {
0208     CFStringRef path = udi.toCFString();
0209     io_registry_entry_t entry = IORegistryEntryCopyFromPath(kIOMasterPortDefault, path);
0210     CFRelease(path);
0211 
0212     // we have to do IOObjectConformsTo - comparing the class names is not good enough
0213     // if (IOObjectConformsTo(entry, kIOEthernetInterfaceClass)) {
0214     //}
0215 
0216     if (entry == MACH_PORT_NULL) {
0217         return 0;
0218     }
0219 
0220     return new IOKitDevice(udi, entry);
0221 }
0222 
0223 }
0224 }
0225 } // namespaces
0226 
0227 #include "moc_iokitmanager.cpp"