Solid

udisksopticaldisc.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Michael Zanetti <mzanetti@kde.org>
3 SPDX-FileCopyrightText: 2010-2012 Lukáš Tinkl <ltinkl@redhat.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "udisksopticaldisc.h"
9#include <fcntl.h>
10#include <sys/stat.h>
11#include <sys/types.h>
12#include <unistd.h>
13
14#include <QMap>
15#include <QSharedMemory>
16#include <QSystemSemaphore>
17#include <QThreadStorage>
18
19#include "soliddefs_p.h"
20#include "udisks2.h"
21#include "udisks_debug.h"
22
23// inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c
24static Solid::OpticalDisc::ContentType advancedDiscDetect(const QByteArray &device_file)
25{
26 /* the discs block size */
27 unsigned short bs;
28 /* the path table size */
29 unsigned short ts;
30 /* the path table location (in blocks) */
31 unsigned int tl;
32 /* length of the directory name in current path table entry */
33 unsigned char len_di = 0;
34 /* the number of the parent directory's path table entry */
35 unsigned int parent = 0;
36 /* filename for the current path table entry */
37 char dirname[256];
38 /* our position into the path table */
39 int pos = 0;
40 /* the path table record we're on */
41 int curr_record = 1;
42 /* import debug category */
43 using Solid::Backends::UDisks2::UDISKS2;
44
45 Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent;
46
47 int fd = open(device_file.constData(), O_RDONLY);
48
49 /* read the block size */
50 lseek(fd, 0x8080, SEEK_CUR);
51 if (read(fd, &bs, 2) != 2) {
52 qCDebug(UDISKS2, "Advanced probing on %s failed while reading block size", device_file.constData());
53 goto out;
54 }
55
56 /* read in size of path table */
57 lseek(fd, 2, SEEK_CUR);
58 if (read(fd, &ts, 2) != 2) {
59 qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table size", device_file.constData());
60 goto out;
61 }
62
63 /* read in which block path table is in */
64 lseek(fd, 6, SEEK_CUR);
65 if (read(fd, &tl, 4) != 4) {
66 qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table block", device_file.constData());
67 goto out;
68 }
69
70 /* seek to the path table */
71 lseek(fd, bs * tl, SEEK_SET);
72
73 /* loop through the path table entries */
74 while (pos < ts) {
75 /* get the length of the filename of the current entry */
76 if (read(fd, &len_di, 1) != 1) {
77 qCDebug(UDISKS2, "Advanced probing on %s failed, cannot read more entries", device_file.constData());
78 break;
79 }
80
81 /* get the record number of this entry's parent
82 i'm pretty sure that the 1st entry is always the top directory */
83 lseek(fd, 5, SEEK_CUR);
84 if (read(fd, &parent, 2) != 2) {
85 qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read parent entry", device_file.constData());
86 break;
87 }
88
89 /* read the name */
90 if (read(fd, dirname, len_di) != len_di) {
91 qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read the entry name", device_file.constData());
92 break;
93 }
94 dirname[len_di] = 0;
95
96 /* if we found a folder that has the root as a parent, and the directory name matches
97 one of the special directories then set the properties accordingly */
98 if (parent == 1) {
99 if (!strcasecmp(dirname, "VIDEO_TS")) {
100 qCDebug(UDISKS2, "Disc in %s is a Video DVD", device_file.constData());
101 result = Solid::OpticalDisc::VideoDvd;
102 break;
103 } else if (!strcasecmp(dirname, "BDMV")) {
104 qCDebug(UDISKS2, "Disc in %s is a Blu-ray video disc", device_file.constData());
105 result = Solid::OpticalDisc::VideoBluRay;
106 break;
107 } else if (!strcasecmp(dirname, "VCD")) {
108 qCDebug(UDISKS2, "Disc in %s is a Video CD", device_file.constData());
109 result = Solid::OpticalDisc::VideoCd;
110 break;
111 } else if (!strcasecmp(dirname, "SVCD")) {
112 qCDebug(UDISKS2, "Disc in %s is a Super Video CD", device_file.constData());
113 result = Solid::OpticalDisc::SuperVideoCd;
114 break;
115 }
116 }
117
118 /* all path table entries are padded to be even,
119 so if this is an odd-length table, seek a byte to fix it */
120 if (len_di % 2 == 1) {
121 lseek(fd, 1, SEEK_CUR);
122 pos++;
123 }
124
125 /* update our position */
126 pos += 8 + len_di;
127 curr_record++;
128 }
129
130 close(fd);
131 return result;
132
133out:
134 /* go back to the start of the file */
135 lseek(fd, 0, SEEK_SET);
136 close(fd);
137 return result;
138}
139
140using namespace Solid::Backends::UDisks2;
141
142class ContentTypesCache
143{
144public:
145 ContentTypesCache()
146 : m_n(0)
147 {
148 }
149
150 void add(const OpticalDisc::Identity &key, Solid::OpticalDisc::ContentTypes content)
151 {
152 if (!find(key)) {
153 m_n = qMin(m_n + 1, sizeof(m_info) / sizeof(*m_info));
154 moveToFront(m_n - 1);
155 front().first = key;
156 }
157 front().second = content;
158 }
159
160 bool find(const OpticalDisc::Identity &key)
161 {
162 for (size_t i = 0; i < m_n; i++) {
163 if (m_info[i].first == key) {
164 moveToFront(i);
165 return true;
166 }
167 }
168 return false;
169 }
170
171 QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> &front()
172 {
173 return *m_info;
174 }
175
176private:
177 void moveToFront(size_t i)
178 {
179 while (i) {
180 qSwap(m_info[i - 1], m_info[i]);
181 --i;
182 }
183 }
184
185 size_t m_n;
186 QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> m_info[100];
187};
188
189class SharedContentTypesCache
190{
191private:
192 ContentTypesCache *m_pointer;
193 QSystemSemaphore m_semaphore;
194 QSharedMemory m_shmem;
195
196 struct Unlocker {
197 public:
198 Unlocker(QSharedMemory *mem)
199 : m_mem(mem)
200 {
201 }
202 ~Unlocker()
203 {
204 m_mem->unlock();
205 }
206 Unlocker(const Unlocker &) = delete;
207 Unlocker &operator=(const Unlocker &) = delete;
208
209 private:
210 QSharedMemory *m_mem;
211 };
212
213 struct Releaser {
214 public:
215 Releaser(QSystemSemaphore *sem)
216 : m_sem(sem)
217 {
218 }
219 ~Releaser()
220 {
221 m_sem->release();
222 }
223 Releaser(const Releaser &) = delete;
224 Releaser &operator=(const Releaser &) = delete;
225
226 private:
227 QSystemSemaphore *m_sem;
228 };
229
230 static QString getKey()
231 {
232 static const QString keyTemplate(QStringLiteral("solid-disk-info-1-%1-%2"));
233 static const QString tableSize(QString::number(sizeof(ContentTypesCache)));
234
235 return keyTemplate.arg(tableSize, QString::number(geteuid()));
236 }
237
238public:
239 SharedContentTypesCache()
240 : m_pointer(nullptr)
241 , m_semaphore(getKey() + QStringLiteral("sem"), 1)
242 , m_shmem(getKey() + QStringLiteral("mem"))
243 {
244 if (!m_semaphore.acquire()) {
245 return;
246 }
247 Releaser releaser(&m_semaphore);
248
249 if (m_shmem.attach()) {
250 m_pointer = reinterpret_cast<ContentTypesCache *>(m_shmem.data());
251 return;
252 }
253
254 if (!m_shmem.create(sizeof(ContentTypesCache))) {
255 return;
256 }
257
258 if (!m_shmem.lock()) {
259 m_shmem.detach();
260 return;
261 }
262 Unlocker unlocker(&m_shmem);
263
264 m_pointer = new (m_shmem.data()) ContentTypesCache;
265 }
266
267 Solid::OpticalDisc::ContentTypes getContent(const OpticalDisc::Identity &info, const QByteArray &file)
268 {
269 if (!m_pointer) {
270 return advancedDiscDetect(file);
271 }
272
273 if (!m_semaphore.acquire()) {
274 return advancedDiscDetect(file);
275 }
276 Releaser releaser(&m_semaphore);
277
278 if (!m_shmem.lock()) {
279 return advancedDiscDetect(file);
280 }
281 Unlocker unlocker(&m_shmem);
282
283 if (!m_pointer->find(info)) {
284 m_pointer->add(info, advancedDiscDetect(file));
285 }
286
287 Solid::OpticalDisc::ContentTypes content = m_pointer->front().second;
288 return content;
289 }
290
291 ~SharedContentTypesCache()
292 {
293 m_semaphore.acquire();
294 Releaser releaser(&m_semaphore);
295 m_shmem.detach();
296 }
297};
298
299Q_GLOBAL_STATIC(QThreadStorage<SharedContentTypesCache>, sharedContentTypesCache)
300
301OpticalDisc::Identity::Identity()
302 : m_detectTime(0)
303 , m_size(0)
304 , m_labelHash(0)
305{
306}
307
308OpticalDisc::Identity::Identity(const Device &device, const Device &drive)
309 : m_detectTime(drive.prop(QStringLiteral("TimeMediaDetected")).toLongLong())
310 , m_size(device.prop(QStringLiteral("Size")).toLongLong())
311 , m_labelHash(qHash(device.prop(QStringLiteral("IdLabel")).toString()))
312{
313}
314
315bool OpticalDisc::Identity::operator==(const OpticalDisc::Identity &b) const
316{
317 /* clang-format off */
318 return m_detectTime == b.m_detectTime
319 && m_size == b.m_size
320 && m_labelHash == b.m_labelHash;
321 /* clang-format on */
322}
323
324OpticalDisc::OpticalDisc(Device *dev)
325 : StorageVolume(dev)
326{
327#if UDEV_FOUND
328 UdevQt::Client client(this);
329 m_udevDevice = client.deviceByDeviceFile(device());
330 // qDebug() << "udev device:" << m_udevDevice.name() << "valid:" << m_udevDevice.isValid();
331 /*qDebug() << "\tProperties:" << */ m_udevDevice.deviceProperties(); // initialize the properties DB so that it doesn't crash further down, #298416
332#endif
333
334 m_drive = new Device(m_device->drivePath());
335}
336
337OpticalDisc::~OpticalDisc()
338{
339 delete m_drive;
340}
341
342qulonglong OpticalDisc::capacity() const
343{
344 return m_device->prop(QStringLiteral("Size")).toULongLong();
345}
346
347bool OpticalDisc::isRewritable() const
348{
349 // the hard way, udisks has no notion of a disc "rewritability"
350 const QString mediaType = media();
351 /* clang-format off */
352 return mediaType == QLatin1String("optical_cd_rw")
353 || mediaType == QLatin1String("optical_dvd_rw")
354 || mediaType == QLatin1String("optical_dvd_ram")
355 || mediaType == QLatin1String("optical_dvd_plus_rw")
356 || mediaType == QLatin1String("optical_dvd_plus_rw_dl")
357 || mediaType == QLatin1String("optical_bd_re")
358 || mediaType == QLatin1String("optical_hddvd_rw");
359 /* clang-format on */
360}
361
362bool OpticalDisc::isBlank() const
363{
364 return m_drive->prop(QStringLiteral("OpticalBlank")).toBool();
365}
366
367bool OpticalDisc::isAppendable() const
368{
369 // qDebug() << "appendable prop" << m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE");
370#if UDEV_FOUND
371 return m_udevDevice.deviceProperty(QStringLiteral("ID_CDROM_MEDIA_STATE")).toString() == QLatin1String("appendable");
372#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
373 return m_device->prop(QStringLiteral("bsdisks_IsAppendable")).toBool();
374#else
375#error Implement this or stub this out for your platform
376#endif
377}
378
379Solid::OpticalDisc::DiscType OpticalDisc::discType() const
380{
381 const QMap<Solid::OpticalDisc::DiscType, QString> map{
382 {Solid::OpticalDisc::CdRom, QStringLiteral("optical_cd")},
383 {Solid::OpticalDisc::CdRecordable, QStringLiteral("optical_cd_r")},
384 {Solid::OpticalDisc::CdRewritable, QStringLiteral("optical_cd_rw")},
385 {Solid::OpticalDisc::DvdRom, QStringLiteral("optical_dvd")},
386 {Solid::OpticalDisc::DvdRecordable, QStringLiteral("optical_dvd_r")},
387 {Solid::OpticalDisc::DvdRewritable, QStringLiteral("optical_dvd_rw")},
388 {Solid::OpticalDisc::DvdRam, QStringLiteral("optical_dvd_ram")},
389 {Solid::OpticalDisc::DvdPlusRecordable, QStringLiteral("optical_dvd_plus_r")},
390 {Solid::OpticalDisc::DvdPlusRewritable, QStringLiteral("optical_dvd_plus_rw")},
391 {Solid::OpticalDisc::DvdPlusRecordableDuallayer, QStringLiteral("optical_dvd_plus_r_dl")},
392 {Solid::OpticalDisc::DvdPlusRewritableDuallayer, QStringLiteral("optical_dvd_plus_rw_dl")},
393 {Solid::OpticalDisc::BluRayRom, QStringLiteral("optical_bd")},
394 {Solid::OpticalDisc::BluRayRecordable, QStringLiteral("optical_bd_r")},
395 {Solid::OpticalDisc::BluRayRewritable, QStringLiteral("optical_bd_re")},
396 {Solid::OpticalDisc::HdDvdRom, QStringLiteral("optical_hddvd")},
397 {Solid::OpticalDisc::HdDvdRecordable, QStringLiteral("optical_hddvd_r")},
398 {Solid::OpticalDisc::HdDvdRewritable, QStringLiteral("optical_hddvd_rw")},
399 };
400 // TODO add these to Solid
401 // map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo";
402 // map[Solid::OpticalDisc::MountRainer] ="optical_mrw";
403 // map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w";
404
405 return map.key(media(), Solid::OpticalDisc::UnknownDiscType); // FIXME optimize, lookup by value, not key
406}
407
408Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const
409{
410 if (isBlank()) {
411 return Solid::OpticalDisc::NoContent;
412 }
413
414 Solid::OpticalDisc::ContentTypes content = Solid::OpticalDisc::NoContent;
415 const bool hasData = m_drive->prop(QStringLiteral("OpticalNumDataTracks")).toUInt() > 0;
416 const bool hasAudio = m_drive->prop(QStringLiteral("OpticalNumAudioTracks")).toUInt() > 0;
417
418 if (hasData) {
419 content |= Solid::OpticalDisc::Data;
420
421 Identity newIdentity(*m_device, *m_drive);
422 if (!(m_identity == newIdentity)) {
423 QByteArray deviceFile(m_device->prop(QStringLiteral("Device")).toByteArray());
424 m_cachedContent = sharedContentTypesCache->localData().getContent(newIdentity, deviceFile);
425 m_identity = newIdentity;
426 }
427
428 content |= m_cachedContent;
429 }
430 if (hasAudio) {
431 content |= Solid::OpticalDisc::Audio;
432 }
433
434 return content;
435}
436
437QString OpticalDisc::media() const
438{
439 return m_drive->prop(QStringLiteral("Media")).toString();
440}
441
442#include "moc_udisksopticaldisc.cpp"
QFlags< ContentType > ContentTypes
Stores a combination of ContentType values.
DiscType
This enum type defines the type of optical disc it can be.
ContentType
This enum type defines the type of content available in an optical disc.
char * toString(const EngineQuery &query)
QVariant read(const QByteArray &data, int versionOverride=0)
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
QAction * open(const QObject *recvr, const char *slot, QObject *parent)
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
const char * constData() const const
QString number(double n, char format, int precision)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 12:06:34 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.