Solid

iokitopticaldrive.cpp
1/*
2 SPDX-FileCopyrightText: 2017 René J.V. Bertin <rjvbertin@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "iokitopticaldrive.h"
8
9#include <QDebug>
10#include <QProcess>
11
12#ifdef EJECT_USING_DISKARBITRATION
13// for QCFType:
14#include <private/qcore_mac_p.h>
15#else
16#include <QStandardPaths>
17#endif
18
19#include <CoreFoundation/CoreFoundation.h>
20#include <DiskArbitration/DiskArbitration.h>
21#include <IOKit/scsi/IOSCSIMultimediaCommandsDevice.h>
22
23using namespace Solid::Backends::IOKit;
24
25class IOKitOpticalDrive::Private
26{
27public:
28 Private(const IOKitDevice *device, const QVariantMap &devCharMap)
29 : m_device(device)
30 , m_deviceCharacteristics(devCharMap)
31 {
32 }
33 virtual ~Private()
34 {
35 }
36
37 QVariant property(const QString &key) const
38 {
39 return m_deviceCharacteristics.value(key);
40 }
41
42 const IOKitDevice *m_device;
43 const QVariantMap m_deviceCharacteristics;
44
48
49#ifdef EJECT_USING_DISKARBITRATION
50 // DiskArbitration-based ejection based on the implementation in libcdio's osx.c
51 // in turn based on VideoLAN (VLC) code.
52 // Not activated by default ATM because I have only been able to test it with the
53 // solid-hardware6 utility and that one remains stuck after a successful return
54 // from IOKitOpticalDrive::eject(). It does so too when using the hdiutil external
55 // utility which cannot be due to using QProcess (to the best of my knowledge).
56 // NB: the full-fledged approach using a cancel sourc ref (cancel_signal) etc. may
57 // well be too complicated.
58
59 typedef struct DAContext {
60 const IOKitDevice *device;
61 int success;
62 bool completed;
63 DASessionRef session;
64 CFRunLoopRef runloop;
65 CFRunLoopSourceRef cancel_signal;
66 } DAContext;
67
68 static void cancelEjectRunloop(void *){};
69
70 static void daEjectCallback(DADiskRef disk, DADissenterRef dissenter, void *context)
71 {
72 Q_UNUSED(disk);
73 DAContext *daContext = static_cast<DAContext *>(context);
74
75 if (dissenter) {
76 CFStringRef status = DADissenterGetStatusString(dissenter);
77 if (status) {
78 qWarning() << "Warning while ejecting" << daContext->device->property("BSD Name").toString() << ":" << QString::fromCFString(status);
79 CFRelease(status);
80 }
81 }
82
83 daContext->success = dissenter ? false : true;
84 daContext->completed = TRUE;
85 CFRunLoopSourceSignal(daContext->cancel_signal);
86 CFRunLoopWakeUp(daContext->runloop);
87 }
88
89 static void daUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void *context)
90 {
91 DAContext *daContext = (DAContext *)context;
92
93 if (!dissenter) {
94 DADiskEject(disk, kDADiskEjectOptionDefault, daEjectCallback, context);
95 daContext->success = (daContext->success == -1 ? true : daContext->success);
96 } else {
97 daContext->success = false;
98 daContext->completed = true;
99 CFRunLoopSourceSignal(daContext->cancel_signal);
100 CFRunLoopWakeUp(daContext->runloop);
101 }
102 }
103
104 bool eject(double timeoutSeconds)
105 {
106 CFDictionaryRef description = nullptr;
107 CFRunLoopSourceContext cancelRunLoopSourceContext = {.perform = cancelEjectRunloop};
108 DAContext daContext = {m_device, -1, false, 0, CFRunLoopGetCurrent(), 0};
109 QCFType<CFRunLoopSourceRef> cancel = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &cancelRunLoopSourceContext);
110 if (!(daContext.cancel_signal = cancel)) {
111 qWarning() << Q_FUNC_INFO << "failed to create cancel runloop source";
112 return false;
113 }
114 QCFType<DASessionRef> session = DASessionCreate(kCFAllocatorDefault);
115 if (!(daContext.session = session)) {
116 qWarning() << Q_FUNC_INFO << "failed to create DiskArbitration session";
117 return false;
118 }
119 const QString devName = m_device->property(QStringLiteral("BSD Name")).toString();
120 QCFType<DADiskRef> daRef = DADiskCreateFromBSDName(kCFAllocatorDefault, daContext.session, devName.toStdString().c_str());
121 if (!daRef) {
122 qWarning() << Q_FUNC_INFO << "failed to create DiskArbitration reference for" << devName;
123 return false;
124 }
125 description = DADiskCopyDescription(daRef);
126 if (description) {
127 DASessionScheduleWithRunLoop(daContext.session, daContext.runloop, kCFRunLoopDefaultMode);
128 CFRunLoopAddSource(daContext.runloop, daContext.cancel_signal, kCFRunLoopDefaultMode);
129 if (CFDictionaryGetValueIfPresent(description, kDADiskDescriptionVolumePathKey, nullptr)) {
130 DADiskUnmount(daRef, kDADiskUnmountOptionWhole, daUnmountCallback, &daContext);
131 }
132 DADiskEject(daRef, kDADiskEjectOptionDefault, daEjectCallback, &daContext);
133 daContext.success = (daContext.success == -1 ? true : daContext.success);
134 while (!daContext.completed) {
135 if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeoutSeconds, true) == kCFRunLoopRunTimedOut) {
136 break;
137 }
138 }
139 if (daContext.completed) {
140 qWarning() << Q_FUNC_INFO << "ejected" << devName;
141 } else {
142 qWarning() << Q_FUNC_INFO << "timeout ejecting" << devName;
143 }
144 CFRunLoopRemoveSource(daContext.runloop, daContext.cancel_signal, kCFRunLoopDefaultMode);
145 DASessionSetDispatchQueue(daContext.session, 0);
146 DASessionUnscheduleFromRunLoop(daContext.session, daContext.runloop, kCFRunLoopDefaultMode);
147 CFRelease(description);
148 } else {
149 qWarning() << Q_FUNC_INFO << "failed to fetch DiskArbitration description for" << devName;
150 }
151 return daContext.success == -1 ? false : daContext.success;
152 }
153#endif // EJECT_USING_DISKARBITRATION
154};
155
156const QMap<Solid::OpticalDrive::MediumType, uint32_t> IOKitOpticalDrive::Private::cdTypeMap = {
157 {Solid::OpticalDrive::Cdr, kCDFeaturesWriteOnceMask},
158 {Solid::OpticalDrive::Cdrw, kCDFeaturesReWriteableMask},
159};
160const QMap<Solid::OpticalDrive::MediumType, uint32_t> IOKitOpticalDrive::Private::dvdTypeMap = {
161 {Solid::OpticalDrive::Dvd, kDVDFeaturesReadStructuresMask},
162 {Solid::OpticalDrive::Dvdr, kDVDFeaturesWriteOnceMask},
163 {Solid::OpticalDrive::Dvdrw, kDVDFeaturesReWriteableMask},
164 {Solid::OpticalDrive::Dvdram, kDVDFeaturesRandomWriteableMask},
165 {Solid::OpticalDrive::Dvdplusr, kDVDFeaturesPlusRMask},
166 {Solid::OpticalDrive::Dvdplusrw, kDVDFeaturesPlusRWMask},
167 // not supported:
168 // {Solid::OpticalDrive::Dvdplusdl, "dvdplusrdl"}
169 // {Solid::OpticalDrive::Dvdplusdlrw, "dvdplusrwdl"}
170 {Solid::OpticalDrive::HdDvd, kDVDFeaturesHDReadMask},
171 {Solid::OpticalDrive::HdDvdr, kDVDFeaturesHDRMask},
172 {Solid::OpticalDrive::HdDvdrw, kDVDFeaturesHDRWMask},
173};
174const QMap<Solid::OpticalDrive::MediumType, uint32_t> IOKitOpticalDrive::Private::bdTypeMap = {
175 {Solid::OpticalDrive::Bd, kBDFeaturesReadMask},
176 {Solid::OpticalDrive::Bdr, kBDFeaturesWriteMask},
177}; // also Solid::OpticalDrive::Bdre
178
179IOKitOpticalDrive::IOKitOpticalDrive(IOKitDevice *device)
180 : IOKitStorage(device)
181{
182 // walk up the IOKit chain to find the parent that has the "Device Characteristics" property
183 // In the examples I've seen this is always the 2nd parent but if ever that turns out
184 // to be non-guaranteed we'll need to do a true walk.
185 IOKitDevice ioDVDServices(IOKitDevice(device->parentUdi()).parentUdi());
186 QVariantMap devCharMap;
187 if (!ioDVDServices.iOKitPropertyExists(QStringLiteral("Device Characteristics"))) {
188 qWarning() << Q_FUNC_INFO << "Grandparent of" << m_device->udi() << "doesn't have the \"Device Characteristics\" but is" << ioDVDServices.udi();
189 } else {
190 const QVariant devCharVar = ioDVDServices.property(QStringLiteral("Device Characteristics"));
191 devCharMap = devCharVar.toMap();
192 }
193 d = new Private(device, devCharMap);
194}
195
196IOKitOpticalDrive::~IOKitOpticalDrive()
197{
198}
199
200/* clang-format off */
201// // Example properties: QMap(("BSD Major", QVariant(int, 1))
202// ("BSD Minor", QVariant(int, 12))
203// ("BSD Name", QVariant(QString, "disk3"))
204// ("BSD Unit", QVariant(int, 3))
205// ("Content", QVariant(QString, "CD_partition_scheme"))
206// ("Content Hint", QVariant(QString, ""))
207// ("Ejectable", QVariant(bool, true))
208// ("IOBusyInterest", QVariant(QString, "IOCommand is not serializable"))
209// ("IOGeneralInterest", QVariant(QString, "IOCommand is not serializable"))
210// ("IOMediaIcon", QVariant(QVariantMap, QMap(("CFBundleIdentifier", QVariant(QString, "com.apple.iokit.IOCDStorageFamily"))
211// ("IOBundleResourceFile", QVariant(QString, "CD.icns")))))
212// ("Leaf", QVariant(bool, false))
213// ("Open", QVariant(bool, true))
214// ("Preferred Block Size", QVariant(qlonglong, 2352))
215// ("Removable", QVariant(bool, true))
216// ("Size", QVariant(qlonglong, 750932448))
217// ("TOC", QVariant(QByteArray, "\x00\xA7\x01\x01\x01\x10\x00\xA0\x00\x00\x00\x00\x01\x00\x00\x01\x12\x00\xA1\x00\x00\x00\x00\f\x00\x00\x01\x12\x00\xA2\x00\x00\x00\x00""F:J\x01\x12\x00\x01\x00\x00\x00\x00\x00\x02\x00\x01\x12\x00\x02\x00\x00\x00\x00\x07/\b\x01\x12\x00\x03\x00\x00\x00\x00\x12\b\x0E\x01\x12\x00\x04\x00\x00\x00\x00\x17\x12""0\x01\x12\x00\x05\x00\x00\x00\x00\x1B+ \x01\x12\x00\x06\x00\x00\x00\x00 \x11\n\x01\x12\x00\x07\x00\x00\x00\x00!-\n\x01\x12\x00\b\x00\x00\x00\x00'\f\x1F\x01\x12\x00\t\x00\x00\x00\x00-\x13;\x01\x12\x00\n\x00\x00\x00\x00""4%\x1E\x01\x12\x00\x0B\x00\x00\x00\x00""62 \x01\x12\x00\f\x00\x00\x00\x00""C\x06""E"))
218// ("Type", QVariant(QString, "CD-ROM"))
219// ("Whole", QVariant(bool, true))
220// ("Writable", QVariant(bool, false))
221// ("className", QVariant(QString, "IOCDMedia")))
222// // related useful entry: QMap(("Device Characteristics", QVariant(QVariantMap, QMap(("Async Notification", QVariant(bool, false))
223// ("BD Features", QVariant(int, 0))
224// ("CD Features", QVariant(int, 2047))
225// ("DVD Features", QVariant(int, 503))
226// ("Fast Spindown", QVariant(bool, true))
227// ("Loading Mechanism", QVariant(QString, "Slot"))
228// ("Low Power Polling", QVariant(bool, false))
229// ("Power Off", QVariant(bool, true))
230// ("Product Name", QVariant(QString, "DVD-R UJ-8A8"))
231// ("Product Revision Level", QVariant(QString, "HA13"))
232// ("Vendor Name", QVariant(QString, "MATSHITA")))))
233// ("IOCFPlugInTypes", QVariant(QVariantMap, QMap(("97ABCF2C-23CC-11D5-A0E8-003065704866", QVariant(QString, "IOSCSIArchitectureModelFamily.kext/Contents/PlugIns/SCSITaskUserClient.kext/Contents/PlugIns/SCSITaskLib.plugin")))))
234// ("IOGeneralInterest", QVariant(QString, "IOCommand is not serializable"))
235// ("IOMatchCategory", QVariant(QString, "SCSITaskUserClientIniter"))
236// ("IOMinimumSegmentAlignmentByteCount", QVariant(qlonglong, 4))
237// ("IOUserClientClass", QVariant(QString, "SCSITaskUserClient"))
238// ("Protocol Characteristics", QVariant(QVariantMap, QMap(("AHCI Port Number", QVariant(qlonglong, 0))
239// ("ATAPI", QVariant(bool, true))
240// ("Physical Interconnect", QVariant(QString, "SATA"))
241// ("Physical Interconnect Location", QVariant(QString, "Internal"))
242// ("Port Speed", QVariant(QString, "1.5 Gigabit"))
243// ("Read Time Out Duration", QVariant(qlonglong, 15000))
244// ("Retry Count", QVariant(qlonglong, 1))
245// ("Write Time Out Duration", QVariant(qlonglong, 15000)))))
246// ("SCSITaskDeviceCategory", QVariant(QString, "SCSITaskAuthoringDevice"))
247// ("SCSITaskUserClient GUID", QVariant(QByteArray, "\x00]\x0F""F\x80\xFF\xFF\xFFg\xB6\xAB\x1B\x00\x00\x00\x00"))
248// ("className", QVariant(QString, "IODVDServices"))
249// ("device-type", QVariant(QString, "DVD")))
250// // QMap(("CFBundleIdentifier", QVariant(QString, "com.apple.iokit.IODVDStorageFamily"))
251// ("IOClass", QVariant(QString, "IODVDBlockStorageDriver"))
252// ("IOGeneralInterest", QVariant(QString, "IOCommand is not serializable"))
253// ("IOMatchCategory", QVariant(QString, "IODefaultMatchCategory"))
254// ("IOProbeScore", QVariant(int, 0))
255// ("IOPropertyMatch", QVariant(QVariantMap, QMap(("device-type", QVariant(QString, "DVD")))))
256// ("IOProviderClass", QVariant(QString, "IODVDBlockStorageDevice"))
257// ("Statistics", QVariant(QVariantMap, QMap(("Bytes (Read)", QVariant(qlonglong, 578020608))
258// ("Bytes (Write)", QVariant(qlonglong, 0))
259// ("Errors (Read)", QVariant(qlonglong, 0))
260// ("Errors (Write)", QVariant(qlonglong, 0))
261// ("Latency Time (Read)", QVariant(qlonglong, 0))
262// ("Latency Time (Write)", QVariant(qlonglong, 0))
263// ("Operations (Read)", QVariant(qlonglong, 18475))
264// ("Operations (Write)", QVariant(qlonglong, 0))
265// ("Retries (Read)", QVariant(qlonglong, 0))
266// ("Retries (Write)", QVariant(qlonglong, 0))
267// ("Total Time (Read)", QVariant(qlonglong, 219944025102))
268// ("Total Time (Write)", QVariant(qlonglong, 0)))))
269// ("className", QVariant(QString, "IODVDBlockStorageDriver")))
270/* clang-format on */
271
272Solid::OpticalDrive::MediumTypes IOKitOpticalDrive::supportedMedia() const
273{
275
276 uint32_t cdFeatures = d->property(QStringLiteral("CD Features")).toInt();
277 uint32_t dvdFeatures = d->property(QStringLiteral("DVD Features")).toInt();
278 uint32_t bdFeatures = d->property(QStringLiteral("BD Features")).toInt();
279
280 qDebug() << Q_FUNC_INFO << "cdFeatures" << cdFeatures << "dvdFeatures" << dvdFeatures << "bdFeatures" << bdFeatures;
281
282 for (auto it = d->cdTypeMap.cbegin(); it != d->cdTypeMap.cend(); ++it) {
283 if (cdFeatures & it.value()) {
284 supported |= it.key();
285 }
286 }
287 for (auto it = d->dvdTypeMap.cbegin(); it != d->dvdTypeMap.cend(); ++it) {
288 if (dvdFeatures & it.value()) {
289 supported |= it.key();
290 }
291 }
292 for (auto it = d->bdTypeMap.cbegin(); it != d->bdTypeMap.cend(); ++it) {
293 const uint32_t value = it.value();
294 if (bdFeatures & value) {
295 supported |= it.key();
296 if (value == kBDFeaturesWriteMask) {
297 supported |= Solid::OpticalDrive::Bdre;
298 }
299 }
300 }
301
302 return supported;
303}
304
305int IOKitOpticalDrive::readSpeed() const
306{
307 return 0;
308}
309
310int IOKitOpticalDrive::writeSpeed() const
311{
312 return 0;
313}
314
315QList<int> IOKitOpticalDrive::writeSpeeds() const
316{
317 return {};
318}
319
320bool IOKitOpticalDrive::eject()
321{
322#ifdef EJECT_USING_DISKARBITRATION
323 // give the devices 30 seconds to eject
324 int error = !d->eject(30.0);
325#else
326 QProcess ejectJob;
327 int error = ejectJob.execute(
328 QStandardPaths::findExecutable(QStringLiteral("hdiutil")), //
329 {QStringLiteral("detach"), QStringLiteral("-verbose"), QStringLiteral("/dev/") + m_device->property(QStringLiteral("BSD Name")).toString()});
330 if (error) {
331 qWarning() << "hdiutil returned" << error << "trying to eject" << m_device->product();
332 }
333#endif // EJECT_USING_DISKARBITRATION
334 if (error) {
335 Q_EMIT ejectDone(Solid::ErrorType::OperationFailed, QVariant(), m_device->udi());
336 return false;
337 } else {
338 Q_EMIT ejectDone(Solid::ErrorType::NoError, QVariant(), m_device->udi());
339 return true;
340 }
341}
342
343#include "moc_iokitopticaldrive.cpp"
Q_SCRIPTABLE CaptureState status()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KGuiItem cancel()
const_iterator cbegin() const const
const_iterator cend() const const
Q_EMITQ_EMIT
int execute(const QString &program, const QStringList &arguments)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString fromCFString(CFStringRef string)
std::string toStdString() const const
int toInt(bool *ok) const const
QMap< QString, QVariant > toMap() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.