1 /*
2 SPDX-FileCopyrightText: 2009 Harald Fernengel <harry@kdevelop.org>
3 SPDX-FileCopyrightText: 2017 René J.V. Bertin <rjvbertin@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include "iokitdevice.h"
9 #include "iokitbattery.h"
10 #include "iokitgenericinterface.h"
11 #include "iokitopticaldisc.h"
12 #include "iokitopticaldrive.h"
13 #include "iokitprocessor.h"
14 #include "iokitstorage.h"
15 #include "iokitstorageaccess.h"
16 #include "iokitvolume.h"
17
18 #include <QDebug>
19 #include <QSet>
20 #include <QUrl>
21
22 #include <sys/sysctl.h>
23 #include <sys/types.h>
24
25 #include <IOKit/network/IOEthernetInterface.h>
26 #include <IOKit/usb/IOUSBLib.h>
27
28 #include <CoreFoundation/CoreFoundation.h>
29
30 // from cfhelper.cpp
31 extern QMap<QString, QVariant> q_toVariantMap(const CFMutableDictionaryRef &dict);
32 extern bool q_sysctlbyname(const char *name, QString &result);
33
34 typedef QSet<Solid::DeviceInterface::Type> DeviceInterfaceTypes;
35
36 namespace Solid
37 {
38 namespace Backends
39 {
40 namespace IOKit
41 {
42 // returns a solid type from an entry and its properties
typesFromEntry(const io_registry_entry_t & entry,const QMap<QString,QVariant> & properties,Solid::DeviceInterface::Type & mainType)43 static DeviceInterfaceTypes typesFromEntry(const io_registry_entry_t &entry, const QMap<QString, QVariant> &properties, Solid::DeviceInterface::Type &mainType)
44 {
45 DeviceInterfaceTypes types;
46 mainType = Solid::DeviceInterface::Unknown;
47 if (IOObjectConformsTo(entry, "AppleACPICPU")) {
48 mainType = Solid::DeviceInterface::Processor;
49 types << mainType;
50 }
51 if (IOObjectConformsTo(entry, "AppleSmartBattery")) {
52 mainType = Solid::DeviceInterface::Battery;
53 types << mainType;
54 }
55 const QString bsdName = QStringLiteral("BSD Name");
56 const QString leaf = QStringLiteral("Leaf");
57 if (IOObjectConformsTo(entry, "IOCDMedia") //
58 || IOObjectConformsTo(entry, "IODVDMedia") //
59 || IOObjectConformsTo(entry, "IOBDMedia")) {
60 mainType = Solid::DeviceInterface::OpticalDrive;
61 types << mainType << Solid::DeviceInterface::OpticalDisc;
62 }
63 if (properties.contains(bsdName) && properties.value(bsdName).toString().startsWith(QStringLiteral("disk"))) {
64 if ((properties.contains(leaf) && properties.value(leaf).toBool() == false) //
65 || mainType == Solid::DeviceInterface::OpticalDrive) {
66 if (mainType == Solid::DeviceInterface::Unknown) {
67 mainType = Solid::DeviceInterface::StorageDrive;
68 }
69 types << Solid::DeviceInterface::StorageDrive;
70 } else if (mainType == Solid::DeviceInterface::Unknown) {
71 mainType = Solid::DeviceInterface::StorageVolume;
72 }
73 types << Solid::DeviceInterface::StorageVolume;
74 }
75
76 if (types.isEmpty()) {
77 types << mainType;
78 // qWarning() << "unsupported entry" << entry << "with properties" << properties;
79 }
80
81 return types;
82 }
83
84 // gets all properties from an entry into a QMap
getProperties(const io_registry_entry_t & entry)85 static QMap<QString, QVariant> getProperties(const io_registry_entry_t &entry)
86 {
87 CFMutableDictionaryRef propertyDict = 0;
88
89 if (IORegistryEntryCreateCFProperties(entry, &propertyDict, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS) {
90 return QMap<QString, QVariant>();
91 }
92
93 QMap<QString, QVariant> result = q_toVariantMap(propertyDict);
94
95 CFRelease(propertyDict);
96
97 io_name_t className;
98 IOObjectGetClass(entry, className);
99 result["className"] = QString::fromUtf8(className);
100
101 return result;
102 }
103
104 // gets the parent's Udi from an entry
getParentDeviceUdi(const io_registry_entry_t & entry)105 static QString getParentDeviceUdi(const io_registry_entry_t &entry)
106 {
107 io_registry_entry_t parent = 0;
108 kern_return_t ret = IORegistryEntryGetParentEntry(entry, kIOServicePlane, &parent);
109 if (ret != KERN_SUCCESS) {
110 // don't release parent here - docs say only on success
111 return QString();
112 }
113
114 QString result;
115 io_string_t pathName;
116 ret = IORegistryEntryGetPath(parent, kIOServicePlane, pathName);
117 if (ret == KERN_SUCCESS) {
118 result = QString::fromUtf8(pathName);
119 }
120
121 // now we can release the parent
122 IOObjectRelease(parent);
123
124 return result;
125 }
126
computerModel()127 static const QString computerModel()
128 {
129 QString qModel;
130 q_sysctlbyname("hw.model", qModel);
131 return qModel;
132 }
133
134 class IOKitDevicePrivate
135 {
136 public:
IOKitDevicePrivate()137 inline IOKitDevicePrivate()
138 : type({Solid::DeviceInterface::Unknown})
139 , parentDevice(nullptr)
140 {
141 }
~IOKitDevicePrivate()142 ~IOKitDevicePrivate()
143 {
144 if (parentDevice) {
145 delete parentDevice;
146 parentDevice = nullptr;
147 }
148 }
149
150 void init(const QString &udiString, const io_registry_entry_t &entry);
151 IOKitDevice *getParentDevice();
152
153 QString udi;
154 QString parentUdi;
155 QMap<QString, QVariant> properties;
156 DeviceInterfaceTypes type;
157 Solid::DeviceInterface::Type mainType;
158 IOKitDevice *parentDevice;
159 };
160
init(const QString & udiString,const io_registry_entry_t & entry)161 void IOKitDevicePrivate::init(const QString &udiString, const io_registry_entry_t &entry)
162 {
163 Q_ASSERT(entry != MACH_PORT_NULL);
164
165 udi = udiString;
166
167 properties = getProperties(entry);
168
169 parentUdi = getParentDeviceUdi(entry);
170 type = typesFromEntry(entry, properties, mainType);
171 if (udi.contains(QStringLiteral("IOBD")) || udi.contains(QStringLiteral("BD PX"))) {
172 qWarning() << "Solid: BlueRay entry" << entry << "mainType=" << mainType << "typeList:" << type << "with properties" << properties;
173 }
174 if (mainType != Solid::DeviceInterface::Unknown) { }
175
176 IOObjectRelease(entry);
177 }
178
getParentDevice()179 IOKitDevice *IOKitDevicePrivate::getParentDevice()
180 {
181 if (!parentDevice) {
182 parentDevice = new IOKitDevice(parentUdi);
183 }
184 return parentDevice;
185 }
186
IOKitDevice(const QString & udi,const io_registry_entry_t & entry)187 IOKitDevice::IOKitDevice(const QString &udi, const io_registry_entry_t &entry)
188 : d(new IOKitDevicePrivate)
189 {
190 d->init(udi, entry);
191 }
192
IOKitDevice(const QString & udi)193 IOKitDevice::IOKitDevice(const QString &udi)
194 : d(new IOKitDevicePrivate)
195 {
196 if (udi.isEmpty()) {
197 qWarning() << Q_FUNC_INFO << "Tried to create Device from empty UDI";
198 return;
199 }
200
201 io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, udi.toLocal8Bit().constData());
202
203 if (entry == MACH_PORT_NULL) {
204 qWarning() << Q_FUNC_INFO << "Tried to create Device from invalid UDI" << udi;
205 return;
206 }
207
208 d->init(udi, entry);
209 }
210
IOKitDevice(const IOKitDevice & device)211 IOKitDevice::IOKitDevice(const IOKitDevice &device)
212 : d(new IOKitDevicePrivate)
213 {
214 if (device.udi().isEmpty()) {
215 qWarning() << Q_FUNC_INFO << "Tried to create Device from empty UDI";
216 return;
217 }
218
219 io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, device.udi().toLocal8Bit().constData());
220
221 if (entry == MACH_PORT_NULL) {
222 qWarning() << Q_FUNC_INFO << "Tried to create Device from invalid UDI" << device.udi();
223 return;
224 }
225
226 d->init(device.udi(), entry);
227 }
228
~IOKitDevice()229 IOKitDevice::~IOKitDevice()
230 {
231 delete d;
232 }
233
conformsToIOKitClass(const QString & className) const234 bool IOKitDevice::conformsToIOKitClass(const QString &className) const
235 {
236 bool conforms = false;
237 if (!className.isEmpty()) {
238 io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, udi().toLocal8Bit().constData());
239 if (entry != MACH_PORT_NULL) {
240 conforms = IOObjectConformsTo(entry, className.toLocal8Bit().constData());
241 IOObjectRelease(entry);
242 }
243 }
244 return conforms;
245 }
246
udi() const247 QString IOKitDevice::udi() const
248 {
249 return d->udi;
250 }
251
parentUdi() const252 QString IOKitDevice::parentUdi() const
253 {
254 return d->parentUdi;
255 }
256
vendor() const257 QString IOKitDevice::vendor() const
258 {
259 QString vendor;
260 if (parentUdi().isEmpty()) {
261 return QStringLiteral("Apple");
262 }
263 switch (d->mainType) {
264 case Solid::DeviceInterface::Processor:
265 return Processor::vendor();
266 break;
267 case Solid::DeviceInterface::Battery:
268 return property(QStringLiteral("Manufacturer")).toString();
269 break;
270 case Solid::DeviceInterface::StorageDrive:
271 case Solid::DeviceInterface::OpticalDrive:
272 case Solid::DeviceInterface::OpticalDisc:
273 return IOKitStorage(this).vendor();
274 break;
275 case Solid::DeviceInterface::StorageVolume:
276 return IOKitVolume(this).vendor();
277 break;
278 default:
279 return QString();
280 break;
281 }
282 return QString();
283 }
284
product() const285 QString IOKitDevice::product() const
286 {
287 if (parentUdi().isEmpty()) {
288 return computerModel();
289 }
290 switch (d->mainType) {
291 case Solid::DeviceInterface::Processor:
292 return Processor::product();
293 break;
294 case Solid::DeviceInterface::Battery:
295 return property(QStringLiteral("DeviceName")).toString();
296 break;
297 case Solid::DeviceInterface::StorageDrive:
298 case Solid::DeviceInterface::OpticalDrive:
299 case Solid::DeviceInterface::OpticalDisc:
300 return IOKitStorage(this).product();
301 break;
302 case Solid::DeviceInterface::StorageVolume:
303 return IOKitVolume(this).product();
304 break;
305 }
306 return QString(); // TODO
307 }
308
description() const309 QString IOKitDevice::description() const
310 {
311 switch (d->mainType) {
312 case Solid::DeviceInterface::Processor:
313 return QStringLiteral("Processor");
314 break;
315 case Solid::DeviceInterface::Battery:
316 return QStringLiteral("Apple Smart Battery");
317 break;
318 case Solid::DeviceInterface::StorageDrive:
319 case Solid::DeviceInterface::OpticalDrive:
320 case Solid::DeviceInterface::OpticalDisc:
321 return IOKitStorage(this).description();
322 break;
323 case Solid::DeviceInterface::StorageVolume: {
324 const QString volLabel = IOKitVolume(this).description();
325 const QString mountPoint = IOKitStorageAccess(this).filePath();
326 if (volLabel.isEmpty()) {
327 return QUrl::fromLocalFile(mountPoint).fileName();
328 } else if (mountPoint.startsWith(QStringLiteral("/Volumes/"))) {
329 // Mac users will expect to see the name under which the volume is mounted here.
330 return QString(QStringLiteral("%1 (%2)")).arg(QUrl::fromLocalFile(mountPoint).fileName()).arg(volLabel);
331 }
332 return volLabel;
333 break;
334 }
335 }
336 return product(); // TODO
337 }
338
icon() const339 QString IOKitDevice::icon() const
340 {
341 // adapted from HalDevice::icon()
342 if (parentUdi().isEmpty()) {
343 if (computerModel().contains(QStringLiteral("MacBook"))) {
344 return QStringLiteral("computer-laptop");
345 } else {
346 return QStringLiteral("computer");
347 }
348
349 } else if (d->type.contains(Solid::DeviceInterface::StorageDrive)) {
350 IOKitStorage drive(this);
351 Solid::StorageDrive::DriveType driveType = drive.driveType();
352
353 switch (driveType) {
354 case Solid::StorageDrive::Floppy:
355 // why not :)
356 return QStringLiteral("media-floppy");
357 break;
358 case Solid::StorageDrive::CdromDrive: {
359 const IOKitOpticalDisc disc(this);
360 if (disc.availableContent() == Solid::OpticalDisc::Audio) {
361 return QStringLiteral("media-optical-audio");
362 } else
363 switch (disc.discType()) {
364 case Solid::OpticalDisc::CdRom:
365 return QStringLiteral("media-optical-data");
366 break;
367 case Solid::OpticalDisc::CdRecordable:
368 case Solid::OpticalDisc::CdRewritable:
369 return QStringLiteral("media-optical-recordable");
370 break;
371 case Solid::OpticalDisc::BluRayRom:
372 case Solid::OpticalDisc::BluRayRecordable:
373 case Solid::OpticalDisc::BluRayRewritable:
374 return QStringLiteral("media-optical-blu-ray");
375 break;
376 }
377 break;
378 }
379 case Solid::StorageDrive::SdMmc:
380 return QStringLiteral("media-flash-sd-mmc");
381 break;
382 case Solid::StorageDrive::CompactFlash:
383 return QStringLiteral("media-flash-cf");
384 break;
385 }
386 if (drive.bus() == Solid::StorageDrive::Usb) {
387 return QStringLiteral("drive-removable-media-usb");
388 }
389 if (drive.isRemovable()) {
390 return QStringLiteral("drive-removable-media");
391 }
392 return QStringLiteral("drive-harddisk");
393
394 } else if (d->mainType == Solid::DeviceInterface::StorageVolume) {
395 } else if (d->mainType == Solid::DeviceInterface::Battery) {
396 return QStringLiteral("battery");
397 } else if (d->mainType == Solid::DeviceInterface::Processor) {
398 return QStringLiteral("cpu"); // FIXME: Doesn't follow icon spec
399 } else {
400 QString iconName = d->getParentDevice()->icon();
401
402 if (!iconName.isEmpty()) {
403 return iconName;
404 }
405
406 return QStringLiteral("drive-harddisk");
407 }
408 return QString();
409 }
410
emblems() const411 QStringList IOKitDevice::emblems() const
412 {
413 return QStringList(); // TODO
414 }
415
property(const QString & key) const416 QVariant IOKitDevice::property(const QString &key) const
417 {
418 if (!d->properties.contains(key)) {
419 return QObject::property(key.toUtf8());
420 }
421 return d->properties.value(key);
422 }
423
allProperties() const424 QMap<QString, QVariant> IOKitDevice::allProperties() const
425 {
426 return d->properties;
427 }
428
iOKitPropertyExists(const QString & key) const429 bool IOKitDevice::iOKitPropertyExists(const QString &key) const
430 {
431 return d->properties.contains(key);
432 }
433
queryDeviceInterface(const Solid::DeviceInterface::Type & type) const434 bool IOKitDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const
435 {
436 switch (type) {
437 case Solid::DeviceInterface::GenericInterface:
438 return true;
439 break;
440 case Solid::DeviceInterface::StorageAccess:
441 if (d->type.contains(Solid::DeviceInterface::StorageDrive) //
442 || d->type.contains(Solid::DeviceInterface::StorageVolume)) {
443 return true;
444 }
445 break;
446 default:
447 return d->type.contains(type);
448 break;
449 }
450 return false;
451 }
452
createDeviceInterface(const Solid::DeviceInterface::Type & type)453 QObject *IOKitDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type)
454 {
455 QObject *iface = nullptr;
456
457 switch (type) {
458 case Solid::DeviceInterface::GenericInterface:
459 iface = new GenericInterface(this);
460 break;
461 case Solid::DeviceInterface::Processor:
462 if (d->type.contains(Solid::DeviceInterface::Processor)) {
463 iface = new Processor(this);
464 }
465 break;
466 case Solid::DeviceInterface::Battery:
467 if (d->type.contains(Solid::DeviceInterface::Battery)) {
468 iface = new Battery(this);
469 }
470 break;
471 case Solid::DeviceInterface::OpticalDrive:
472 if (d->type.contains(Solid::DeviceInterface::OpticalDrive)) {
473 iface = new IOKitOpticalDrive(this);
474 }
475 break;
476 case Solid::DeviceInterface::OpticalDisc:
477 if (d->type.contains(Solid::DeviceInterface::OpticalDisc)) {
478 iface = new IOKitOpticalDisc(this);
479 }
480 break;
481 case Solid::DeviceInterface::StorageDrive:
482 if (d->type.contains(Solid::DeviceInterface::StorageDrive)) {
483 iface = new IOKitStorage(this);
484 }
485 break;
486 case Solid::DeviceInterface::Block:
487 if (d->type.contains(Solid::DeviceInterface::OpticalDisc)) {
488 iface = new IOKitOpticalDisc(this);
489 } else if (d->type.contains(Solid::DeviceInterface::OpticalDrive)) {
490 iface = new IOKitOpticalDrive(this);
491 } else if (d->type.contains(Solid::DeviceInterface::StorageVolume)) {
492 iface = new IOKitVolume(this);
493 } else if (d->type.contains(Solid::DeviceInterface::StorageDrive)) {
494 iface = new IOKitStorage(this);
495 }
496 break;
497 case Solid::DeviceInterface::StorageVolume:
498 if (d->type.contains(Solid::DeviceInterface::StorageVolume)) {
499 iface = new IOKitVolume(this);
500 }
501 break;
502 case Solid::DeviceInterface::StorageAccess:
503 if (d->type.contains(Solid::DeviceInterface::StorageDrive) //
504 || d->type.contains(Solid::DeviceInterface::StorageVolume)) {
505 iface = new IOKitStorageAccess(this);
506 }
507 break;
508 // the rest is TODO
509 }
510
511 return iface;
512 }
513
514 }
515 }
516 } // namespaces
517