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