1 /*
2     SPDX-FileCopyrightText: 2009 Harald Fernengel <harry@kdevelop.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include "iokitmanager.h"
8 #include "iokitdevice.h"
9 
10 #include <qdebug.h>
11 
12 #include <IOKit/IOKitLib.h>
13 #include <IOKit/network/IOEthernetInterface.h>
14 #include <IOKit/usb/IOUSBLib.h>
15 
16 #include <CoreFoundation/CoreFoundation.h>
17 
18 namespace Solid
19 {
20 namespace Backends
21 {
22 namespace IOKit
23 {
24 class IOKitManagerPrivate
25 {
26 public:
IOKitManagerPrivate()27     inline IOKitManagerPrivate()
28         : port(nullptr)
29         , source(nullptr)
30     {
31     }
32 
33     IONotificationPortRef port;
34     CFRunLoopSourceRef source;
35 
36     static const char *typeToName(Solid::DeviceInterface::Type type);
37     static QStringList devicesFromRegistry(io_iterator_t it);
38 
39     QSet<Solid::DeviceInterface::Type> supportedInterfaces;
40 };
41 
42 // gets all registry paths from an iterator
devicesFromRegistry(io_iterator_t it)43 QStringList IOKitManagerPrivate::devicesFromRegistry(io_iterator_t it)
44 {
45     QStringList result;
46     io_object_t obj;
47     io_string_t pathName;
48     while ((obj = IOIteratorNext(it))) {
49         kern_return_t ret = IORegistryEntryGetPath(obj, kIOServicePlane, pathName);
50         if (ret != KERN_SUCCESS) {
51             qWarning() << Q_FUNC_INFO << "IORegistryEntryGetPath failed";
52             continue;
53         }
54         result += QString::fromUtf8(pathName);
55         ret = IOObjectRelease(obj);
56         if (ret != KERN_SUCCESS) {
57             // very unlikely to happen - keep it a qDebug just in case.
58             // compiler will nuke this code in release builds.
59             qDebug() << Q_FUNC_INFO << "Unable to release object reference";
60         }
61     }
62     IOObjectRelease(it);
63 
64     return result;
65 }
66 
typeToName(Solid::DeviceInterface::Type type)67 const char *IOKitManagerPrivate::typeToName(Solid::DeviceInterface::Type type)
68 {
69     switch (type) {
70     case Solid::DeviceInterface::Unknown:
71         return 0;
72     case Solid::DeviceInterface::Processor:
73         return "AppleACPICPU";
74     case Solid::DeviceInterface::Battery:
75         return "AppleSmartBattery";
76 
77         // Solid::DeviceInterface::GenericInterface:
78         // Solid::DeviceInterface::Block:
79     case Solid::DeviceInterface::StorageAccess:
80     case Solid::DeviceInterface::StorageDrive:
81     case Solid::DeviceInterface::StorageVolume:
82         return "IOMedia";
83     case Solid::DeviceInterface::OpticalDrive:
84     case Solid::DeviceInterface::OpticalDisc:
85         return "IOCDMedia";
86         // Solid::DeviceInterface::Camera:
87         // Solid::DeviceInterface::PortableMediaPlayer:
88     }
89 
90     return 0;
91 }
92 
IOKitManager(QObject * parent)93 IOKitManager::IOKitManager(QObject *parent)
94     : Solid::Ifaces::DeviceManager(parent)
95     , d(new IOKitManagerPrivate)
96 {
97     d->port = IONotificationPortCreate(kIOMasterPortDefault);
98     if (!d->port) {
99         qWarning() << Q_FUNC_INFO << "Unable to create notification port";
100         return;
101     }
102 
103     d->source = IONotificationPortGetRunLoopSource(d->port);
104     if (!d->source) {
105         qWarning() << Q_FUNC_INFO << "Unable to create notification source";
106         return;
107     }
108 
109     CFRunLoopAddSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode);
110     // clang-format off
111     d->supportedInterfaces << Solid::DeviceInterface::GenericInterface
112                            << Solid::DeviceInterface::Processor
113                            << Solid::DeviceInterface::Block
114                            << Solid::DeviceInterface::StorageAccess
115                            << Solid::DeviceInterface::StorageDrive
116                            << Solid::DeviceInterface::OpticalDrive
117                            << Solid::DeviceInterface::StorageVolume
118                            << Solid::DeviceInterface::OpticalDisc
119                            << Solid::DeviceInterface::Camera
120                            << Solid::DeviceInterface::PortableMediaPlayer
121                            << Solid::DeviceInterface::Battery;
122     // clang-format on
123 }
124 
~IOKitManager()125 IOKitManager::~IOKitManager()
126 {
127     if (d->source) {
128         CFRunLoopRemoveSource(CFRunLoopGetCurrent(), d->source, kCFRunLoopDefaultMode);
129     }
130     if (d->port) {
131         IONotificationPortDestroy(d->port);
132     }
133 
134     delete d;
135 }
136 
udiPrefix() const137 QString IOKitManager::udiPrefix() const
138 {
139     return QString(); // FIXME: We should probably use a prefix there... has to be tested on Mac
140 }
141 
supportedInterfaces() const142 QSet<Solid::DeviceInterface::Type> IOKitManager::supportedInterfaces() const
143 {
144     return d->supportedInterfaces;
145 }
146 
allDevices()147 QStringList IOKitManager::allDevices()
148 {
149     // use an IORegistry Iterator to iterate over all devices in the service plane
150 
151     io_iterator_t it;
152     kern_return_t ret = IORegistryCreateIterator(kIOMasterPortDefault, kIOServicePlane, kIORegistryIterateRecursively, &it);
153     if (ret != KERN_SUCCESS) {
154         qWarning() << Q_FUNC_INFO << "unable to create iterator";
155         return QStringList();
156     }
157 
158     return IOKitManagerPrivate::devicesFromRegistry(it);
159 }
160 
devicesFromQuery(const QString & parentUdi,Solid::DeviceInterface::Type type)161 QStringList IOKitManager::devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type)
162 {
163     QStringList result;
164 
165     if (type == Solid::DeviceInterface::Unknown) {
166         // match all device interfaces
167         result = allDevices();
168     } else {
169         const char *deviceClassName = IOKitManagerPrivate::typeToName(type);
170         if (!deviceClassName) {
171             return QStringList();
172         }
173 
174         CFMutableDictionaryRef matchingDict = IOServiceMatching(deviceClassName);
175 
176         if (!matchingDict) {
177             return QStringList();
178         }
179 
180         io_iterator_t it = 0;
181 
182         // note - IOServiceGetMatchingServices dereferences the dict
183         kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &it);
184 
185         result = IOKitManagerPrivate::devicesFromRegistry(it);
186     }
187 
188     // if the parentUdi is an empty string, return all matches
189     if (parentUdi.isEmpty()) {
190         return result;
191     }
192 
193     // return only matches that start with the parent's UDI
194     QStringList filtered;
195     for (const QString &udi : std::as_const(result)) {
196         if (udi.startsWith(parentUdi)) {
197             filtered += udi;
198         }
199     }
200 
201     return filtered;
202 }
203 
createDevice(const QString & udi)204 QObject *IOKitManager::createDevice(const QString &udi)
205 {
206     io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, udi.toLocal8Bit().constData());
207 
208     // we have to do IOObjectConformsTo - comparing the class names is not good enough
209     // if (IOObjectConformsTo(entry, kIOEthernetInterfaceClass)) {
210     //}
211 
212     if (entry == MACH_PORT_NULL) {
213         return 0;
214     }
215 
216     return new IOKitDevice(udi, entry);
217 }
218 
219 }
220 }
221 } // namespaces
222