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